package database

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	configrations "gitee.com/chenjihao00/common-service/configrations/mysql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

type Database struct {
	Host      string `json:"host"`
	Port      int    `json:"port"`
	DBName    string `json:"db_name"`
	Username  string `json:"username"`
	Password  string `json:"password"`
	Charset   string `json:"charset"`
	ParseTime bool   `json:"parse_time"`
	Loc       string `json:"loc"`
}

func (database *Database) DNS() string {
	return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=%s",
		database.Username,
		database.Password,
		database.Host,
		database.Port,
		database.DBName,
		database.Charset,
		database.ParseTime,
		database.Loc,
	)
}

type Mysql struct {
	config   *configrations.MysqlConfigration
	database *Database
	db       *gorm.DB
}

func NewMysql(fileName string, configrDir string, opts ...configrations.MysqlConfigrationOption) *Mysql {
	config := configrations.NewMysqlConfigration(fileName, configrDir, opts...)
	return &Mysql{
		config: config,
	}
}

func (m *Mysql) LoadDatabase() error {
	if m.database != nil {
		return nil
	}
	filePath := filepath.Join(m.config.ConfigDir, m.config.FileName)
	fileAbsPath, err := filepath.Abs(filePath)
	if err != nil {
		return err
	}
	f, err := os.ReadFile(fileAbsPath)
	if err != nil {
		return err
	}
	var datasource Database
	if err := json.Unmarshal(f, &datasource); err != nil {
		return err
	}
	m.database = &datasource
	return nil
}

func (m *Mysql) DB() (*gorm.DB, error) {
	var err error
	if m.db != nil {
		return m.db, nil
	}
	for i := 0; i < m.config.MaxRetries; i++ {
		db, connErr := m.connect()
		if err == nil {
			m.db = db
			break
		}
		delay := m.config.BaseDelay * time.Duration(1+i)
		if delay > m.config.MaxDelay {
			delay = m.config.MaxDelay
		}
		err = connErr
		fmt.Printf("connect to database failed: %s, retry after %s\n", err, delay)
		time.Sleep(delay)
	}
	return m.db, err
}

func (m *Mysql) connect() (*gorm.DB, error) {
	dns := m.database.DNS()
	var logLevel logger.LogLevel
	switch strings.ToLower(m.config.LogLevel) {
	case "silent":
		logLevel = logger.Silent
	case "error":
		logLevel = logger.Error
	case "warn":
		logLevel = logger.Warn
	default:
		logLevel = logger.Info
	}

	gormConfig := &gorm.Config{
		Logger: logger.Default.LogMode(logLevel),
	}

	db, err := gorm.Open(mysql.Open(dns), gormConfig)
	if err != nil {
		return nil, err
	}
	sqlDB, err := db.DB()
	if err != nil {
		return nil, fmt.Errorf("failed to get database instance: %w", err)
	}
	sqlDB.SetMaxOpenConns(m.config.MaxOpenConns)
	sqlDB.SetMaxIdleConns(m.config.MaxIdleConns)
	sqlDB.SetConnMaxLifetime(m.config.ConnMaxLifetime)
	sqlDB.SetConnMaxIdleTime(m.config.ConnMaxIdleTime)
	return db, nil
}

func (m *Mysql) Migrate(model ...interface{}) error {
	db, err := m.DB()
	if err != nil {
		return err
	}
	return db.AutoMigrate(model...)
}
